package com.feilong.cn.mybatis.autoV2.plugin;

import org.apache.ibatis.plugin.Invocation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by yangxulong on 2018/4/9.
 */
public class AutoPlugin implements InvocationHandler {

    private final Object _target;
    private final AutoInterceptor _autoInterceptors;
    private final Map<Class<?>, Set<Method>> _signatureMaps;

    public AutoPlugin(Object target, AutoInterceptor autoInterceptors, Map<Class<?>, Set<Method>> signatureMaps) {
        _target = target;
        _autoInterceptors = autoInterceptors;
        _signatureMaps = signatureMaps;
    }


    public static Object Warp(Object target, AutoInterceptor inc) {

        //将Signature注解中的方法报保存未Map
        Map<Class<?>, Set<Method>> signatureMaps = getSignatureMap(inc);
        if (signatureMaps == null || signatureMaps.size() < 1) {
            return target;
        }
        Class<?>[] interfaces = getInterfaces(target, signatureMaps);
        if (interfaces.length > 0) {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    interfaces,
                    new AutoPlugin(target, inc, signatureMaps));
        } else {
            return target;
        }
    }

    private static Map<Class<?>, Set<Method>> getSignatureMap(AutoInterceptor inc) {
        AutoIntercepts autointercepts = inc.getClass().getAnnotation(AutoIntercepts.class);
        if (autointercepts == null) return null;
        AutoSignature[] signatures = autointercepts.value();
        if (signatures == null || signatures.length < 1) return null;
        Map<Class<?>, Set<Method>> signatureMaps = new HashMap<>();
        for (AutoSignature signature : signatures) {
            Set<Method> methodSet = signatureMaps.get(signature.type());
            if (methodSet == null) {
                methodSet = new HashSet<>();
                signatureMaps.put(signature.type(), methodSet);
            }
            try {
                Method method = signature.type().getMethod(signature.method(), signature.args());
                methodSet.add(method);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("no such method" + signature.type() + "." + signature.method() + "; cause:" + e, e);
            }
        }

        return signatureMaps;
    }

    //当调用Warp时, 判断warp的对象是否与Signature注解中的对象类型一致, 一致时生成代理.
    private static Class<?>[] getInterfaces(Object target, Map<Class<?>, Set<Method>> signatureMaps) {
        Set<Class<?>> setClass = new HashSet<>();

        Class<?> clazz = target.getClass();
        while (clazz != null) {
            for (Class c : clazz.getInterfaces()) {
                if (signatureMaps.containsKey(c)) {
                    setClass.add(c);
                }
            }
            clazz = clazz.getSuperclass();
        }
        return setClass.toArray(new Class<?>[setClass.size()]);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Class clazz = method.getDeclaringClass();
        Set methodSet = this._signatureMaps.get(clazz);
        //拦截执行plugin,
        if (methodSet.contains(method)) {
            return this._autoInterceptors.intercept(new Invocation(this._target, method, args));
        }
        return method.invoke(this._target, args);

    }
}
